using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

// TODO: replace these with the processor input and output types.
using TInput = System.String;
using TOutput = System.String;
using System.ComponentModel;
using System.IO;
using StillDesign.PhysX;
using System.Windows.Forms;
using System.Reflection;

namespace ModelImporter
{

    public class MeshResourceCompiler
    {
        public class MeshResourceCompilerOutput : UserOutputStream
        {
            public MeshResourceCompilerOutput() { }

            public override sealed void Print(string message)
            {
                MessageBox.Show("Output: " + message);
            }
            public override sealed AssertResponse ReportAssertionViolation(string message, string file, int lineNumber)
            {
                MessageBox.Show("Assertion failed:\n" + message + "\nFile " + file);
                return AssertResponse.Continue;
            }
            public override sealed void ReportError(ErrorCode errorCode, string message, string file, int lineNumber)
            {
                MessageBox.Show(errorCode.ToString() + "\n" + message + "\nFile: " + file);
            }
        }

        public static void ProcessMeshVertexData(ModelTestDesc desc, ModelContent model)
        {
            Core core = new Core();

            List<Vector3> Vertices = new List<Vector3>();
            List<int> Indices = new List<int>();
            int curIdx = 0;
            foreach (ModelMeshContent mesh in model.Meshes)
            {
                foreach (GeometryContent g in mesh.SourceMesh.Geometry)
                {
                    foreach (int idx in g.Indices)
                    {
                        Vector3 pos = g.Vertices.Positions[idx];
                        Vertices.Add(pos);

                        desc.BoundsMax.X = (pos.X > desc.BoundsMax.X) ? pos.X : desc.BoundsMax.X;
                        desc.BoundsMax.Y = (pos.Y > desc.BoundsMax.Y) ? pos.Y : desc.BoundsMax.Y;
                        desc.BoundsMax.Z = (pos.Z > desc.BoundsMax.Z) ? pos.Z : desc.BoundsMax.Z;

                        desc.BoundsMin.X = (pos.X < desc.BoundsMin.X) ? pos.X : desc.BoundsMin.X;
                        desc.BoundsMin.Y = (pos.Y < desc.BoundsMin.Y) ? pos.Y : desc.BoundsMin.Y;
                        desc.BoundsMin.Z = (pos.Z < desc.BoundsMin.Z) ? pos.Z : desc.BoundsMin.Z;

                        Indices.Add(curIdx);

                        curIdx++;
                    }
                }
            }

            Vector3[] vertices = new Vector3[Vertices.Count];
            int[] indices = new int[Indices.Count];
            Vertices.CopyTo(vertices);
            Indices.CopyTo(indices);

            MemoryStream s = null;

            // Build triangles
            {
                desc.Triangles = new ModelTestDesc.Triangle[vertices.Length / 3];
                int j = 0;
                for (int i = 0; i < desc.Triangles.Length; i++, j+=3)
                {
                    desc.Triangles[i].p0 = vertices[j + 0];
                    desc.Triangles[i].p1 = vertices[j + 1];
                    desc.Triangles[i].p2 = vertices[j + 2];
                }
            }

            // Build tri mesh
            {
                TriangleMeshDescription triangleMeshDesc = new TriangleMeshDescription();

                triangleMeshDesc.TriangleCount = indices.Length / 3;
                triangleMeshDesc.VertexCount = vertices.Length;

                triangleMeshDesc.Flags = MeshFlag.FlipNormals;

                triangleMeshDesc.AllocateTriangles<int>(triangleMeshDesc.TriangleCount);
                triangleMeshDesc.AllocateVertices<Vector3>(triangleMeshDesc.VertexCount);

                triangleMeshDesc.TriangleStream.SetData(indices);
                triangleMeshDesc.VerticesStream.SetData(vertices);

                s = new MemoryStream();

                bool meshBuilt = true;
                Cooking.InitializeCooking(new MeshResourceCompilerOutput());
                try
                {
                    Cooking.CookTriangleMesh(triangleMeshDesc, s);
                }
                catch (Exception ex)
                {
                    meshBuilt = false;
                }
                Cooking.CloseCooking();
                s.Position = 0;

                if (meshBuilt)
                    desc.TriMesh = s;
            }

            // Build convex
            if (vertices.Length > 3)
            {
                ConvexMeshDescription convexMeshDesc = new ConvexMeshDescription();
                /*
                convexMeshDesc.PointCount = vertices.Length;
                convexMeshDesc.Flags |= ConvexFlag.ComputeConvex;
                convexMeshDesc.AllocatePoints<Vector3>(vertices.Length);
                //convexMeshDesc.AllocateTriangles<int>(indices.Length);

                convexMeshDesc.PointsStream.SetData(vertices);
                //convexMeshDesc.TriangleStream.SetData(indices);
                //convexMeshDesc.TriangleCount = indices.Length / 3;
                */
                convexMeshDesc.PointCount = vertices.Length;
                convexMeshDesc.Flags = ConvexFlag.ComputeConvex | ConvexFlag.InflateConvex;

                convexMeshDesc.AllocatePoints<Vector3>(convexMeshDesc.PointCount);
                for (int j = 0; j < convexMeshDesc.PointCount; j++)
                {
                    convexMeshDesc.PointsStream.Write(vertices[j]);
                }

                s = new MemoryStream();
                Cooking.InitializeCooking(new MeshResourceCompilerOutput());
                Cooking.CookConvexMesh(convexMeshDesc, s);
                Cooking.CloseCooking();
                s.Position = 0;
                desc.ConvexMesh = s;
            }

            core.Dispose();
        }
    }

    [ContentProcessor(DisplayName = "TestXNA3 processor")]
    public class ModelImporterProcessor : ModelProcessor
    {
        [Browsable(false)]
        public override bool GenerateTangentFrames
        {
            get { return true; }
            set { }
        }

        public ModelImporterProcessor()
        {
            if (System.Diagnostics.Process.GetCurrentProcess().ProcessName != "VCSExpress")
            {
                if (System.Windows.Forms.MessageBox.Show("Convert from 3dsmax space?", "Convert", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    RotationX = -90.0f;
                }
            }
        }

        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            ModelContent content = base.Process(input, context);
            ModelTestDesc desc = new ModelTestDesc();
            MeshResourceCompiler.ProcessMeshVertexData(desc, content);
            content.Tag = desc;
            return content;
        }
        
        protected override void ProcessVertexChannel(GeometryContent geometry,
                                                    int vertexChannelIndex, ContentProcessorContext context)
        {
            base.ProcessVertexChannel(geometry, vertexChannelIndex, context);
        }

        [DisplayName("Shader")]
        [Description("This shader will be used with model")]
        [DefaultValue("StaticMeshDefaultShader")]
        public string Shader
        {
            get { return shader; }
            set { shader = value; }
        }
        private string shader = "StaticMeshDefaultShader";

        protected override MaterialContent ConvertMaterial(MaterialContent material,
                                                                                ContentProcessorContext context)
        {
            //EffectMaterialContent staticMeshMaterial = new EffectMaterialContent();
            //staticMeshMaterial.Effect = new ExternalReference<EffectContent>("Shaders/" + shader + ".fx");

            return null;// context.Convert<MaterialContent, MaterialContent>(staticMeshMaterial, typeof(MaterialProcessor).Name);
        }
    }
}